using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace XGame.Input
{

    namespace EventArgs
    {

        /// <summary>
        /// The XInputEventArgs object is for passing around event objects to the XScreen objects
        /// to be processed individually. If PropigateEvents is set to false, this class will be
        /// nulled out on the next call.
        /// </summary>
        public class XInputEventArgs : System.EventArgs
        {

            /// <summary>
            /// The MouseStructure object contains all the different
            /// events that a mouse can have occur on it. If an object
            /// returns as null, then that event did not fire in the frame.
            /// </summary>
            public struct MouseStructure
            {

                /// <summary>
                /// The ScrollWheel describes the current state of the scroll wheel on the mouse.
                /// It also, for the frame, indicates whether or not the scroll wheel is going up,
                /// down, or if it hasn't moved at all.
                /// </summary>
                public MouseScrollWheelEventArgs ScrollWheel { get; set; }

                /// <summary>
                /// The MouseMoved object describes the location the mouse is currently at, if the
                /// location of the mouse needs to be updated
                /// </summary>
                public MouseMoveEventArgs MouseMoved { get; set; }

                /// <summary>
                /// The MouseDowns collection contains a series of buttons that are currently pressed
                /// </summary>
                public IEnumerable<MouseDownEventArgs> MouseDowns { get; set; }

                /// <summary>
                /// The MouseUps collection shows what mouse buttons have been released that were
                /// pressed the previous frame
                /// </summary>
                public IEnumerable<MouseUpEventArgs> MouseUps { get; set; }

                /// <summary>
                /// The MouseHelds collection shows what mouse buttons have been held for a specified duration, as well
                /// as the duration of how long the button has been held down
                /// </summary>
                public IEnumerable<MouseHeldEventArgs> MouseHelds { get; set; }

            }


            /// <summary>
            /// A collection of potential mouse actions that occurred since the last update
            /// </summary>
            public MouseStructure Mouse { get; set; }

            /// <summary>
            /// The pressed object includes a collection of all keys that were pressed
            /// </summary>
            public KeysPressedEventArgs Pressed { get; set; }

            /// <summary>
            /// The released object includes a collection of keys that were released since the last frame
            /// </summary>
            public KeysReleasedEventArgs Released { get; set; }

        }
        

        /// <summary>
        /// A generic mouse action has occurred.
        /// The class contains a button that is currently being pressed as well as the last known location of the cursor
        /// </summary>
        public class MouseActionEventArgs : System.EventArgs
        {

            /// <summary>
            /// The button that is being pressed
            /// </summary>
            public XConstants.ButtonType Button { get; set; }

            /// <summary>
            /// The current location of the cursor
            /// </summary>
            public Point Location { get; set; }

        }

        /// <summary>
        /// Occurs whenever the scroll wheel value is different than prior
        /// </summary>
        public class MouseScrollWheelEventArgs : MouseActionEventArgs
        {

            /// <summary>
            /// The direction that the scroll wheel is moving
            /// </summary>
            public enum Direction
            {

                /// <summary>
                /// The scroll wheel is moving up
                /// </summary>
                Up,

                /// <summary>
                /// The scroll wheel is moving down
                /// </summary>
                Down,

                /// <summary>
                /// The wheel has not moved since the last frame
                /// </summary>
                None

            }

            /// <summary>
            /// The current Scroll Wheel Value
            /// </summary>
            public Int32 Value { get; set; }

            /// <summary>
            /// The direction of the scroll wheel
            /// </summary>
            public Direction WheelDirection { get; set; }

        }

        /// <summary>
        /// Occurs whenever the mouse moves from its prior location
        /// </summary>
        public class MouseMoveEventArgs : MouseActionEventArgs { }

        /// <summary>
        /// Occurs when a mouse button is released
        /// </summary>
        public class MouseUpEventArgs : MouseActionEventArgs { }

        /// <summary>
        /// Occurs when a mouse button is pressed
        /// </summary>
        public class MouseDownEventArgs : MouseActionEventArgs { }

        /// <summary>
        /// These event arguments are only used because a mouse button was held beyond the duration specified in the XMouse class.
        /// The class will report which button was held and for how long it was held.
        /// </summary>
        public class MouseHeldEventArgs : MouseActionEventArgs
        {

            /// <summary>
            /// The duration that the button has been held down
            /// </summary>
            public TimeSpan Duration { get; set; }

        }


        /// <summary>
        /// A generic event argument class for the XKeyboard object
        /// </summary>
        public class KeyActionEventArgs : System.EventArgs { }

        /// <summary>
        /// Whenever a keyboard key (or keys) are registered as being pressed
        /// and not ignored, they will be passed in this event argument class
        /// for a screen to process.
        /// </summary>
        public class KeysPressedEventArgs : KeyActionEventArgs
        {

            /// <summary>
            /// A collection of keys that are currently being pressed
            /// </summary>
            public IEnumerable<Keys> Keys { get; set; }

        }

        /// <summary>
        /// A key was just released.
        /// </summary>
        public class KeysReleasedEventArgs : KeyActionEventArgs
        {

            /// <summary>
            /// A collection of keys that were released from the last frame
            /// </summary>
            public IEnumerable<Keys> Keys { get; set; }

        }

    }
    
    /// <summary>
    /// The XMouse class handles all mouse events for XGame, including mouse up/down/held events and Mouse Movement events
    /// </summary>
    public class XMouse : XUpdate
    {


        private Dictionary<XConstants.ButtonType, TimeSpan> iTimeWatcher;
        private Point iLastPoint;
        private Int32 iLastValue;

        
        /// <summary>
        /// Gets or sets the duration that must pass before a MouseHeld event is raised whenever a mouse button is pressed
        /// </summary>
        public TimeSpan Duration { get; set; }

        /// <summary>
        /// A boolean flag that indicates whether or not the cursor events shall capture OffScreen events
        /// </summary>
        public Boolean CaptureOffScreen { get; set; }

        /// <summary>
        /// A collection of mouse events that have occurred since the previous frame
        /// </summary>
        public IEnumerable<EventArgs.MouseActionEventArgs> Events { get; private set; }
        
        /// <summary>
        /// Updates the XMouse class and allows the logic to run for detecting a MouseDown or MouseHeld event
        /// </summary>
        /// <param name="aTime">The current gameTime object</param>
        public void Update(GameTime aTime)
        {
            
            // Get the current mouse state
            MouseState oCurrentState = Mouse.GetState();
            Point oLocation = new Point(oCurrentState.X, oCurrentState.Y);

            // Clear out the events from the last frame!
            Events = new List<EventArgs.MouseActionEventArgs>().AsEnumerable();

            #region Movement

            // Check the locations
            if (iLastPoint != oLocation)
            {
                (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseMoveEventArgs { Button = XConstants.ButtonType.None, Location = oLocation });
                iLastPoint = oLocation;
            }

            #endregion

            #region Scroll Wheel

            if (iLastValue != oCurrentState.ScrollWheelValue)
            {
                (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseScrollWheelEventArgs
                {
                    Button = XConstants.ButtonType.ScrollWheel,
                    Location = oLocation,
                    Value = oCurrentState.ScrollWheelValue,
                    WheelDirection = iLastValue < oCurrentState.ScrollWheelValue ? EventArgs.MouseScrollWheelEventArgs.Direction.Up : EventArgs.MouseScrollWheelEventArgs.Direction.Down
                });
                iLastValue = oCurrentState.ScrollWheelValue;
            }
            else
            {
                (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseScrollWheelEventArgs
                {
                    Button = XConstants.ButtonType.ScrollWheel,
                    Location = oLocation,
                    Value = oCurrentState.ScrollWheelValue,
                    WheelDirection = EventArgs.MouseScrollWheelEventArgs.Direction.None
                });
            }

            #endregion

            #region Left

            // Now, check the left, middle, and right buttons to see if their state is pressed
            if (oCurrentState.LeftButton == ButtonState.Pressed)
            {

                // Let's see if it's in the dictionary
                if (!iTimeWatcher.ContainsKey(XConstants.ButtonType.LeftButton))
                {
                    iTimeWatcher.Add(XConstants.ButtonType.LeftButton, aTime.TotalGameTime);
                    (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseDownEventArgs
                    {
                        Button = XConstants.ButtonType.LeftButton,
                        Location = oLocation
                    });
                }
                else
                {

                    // It does contain the key; let's check to see if we need to raise an event
                    if (aTime.TotalGameTime - iTimeWatcher[XConstants.ButtonType.LeftButton] > Duration)
                    {
                        // Raise the event!
                        (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseHeldEventArgs
                        {
                            Button = XConstants.ButtonType.LeftButton,
                            Location = oLocation,
                            Duration = aTime.TotalGameTime - iTimeWatcher[XConstants.ButtonType.LeftButton]
                        });
                    }

                }

            }
            else
            {

                // It is not pressed. See if we exist in the collection.
                // If so, remove it and raise the OnMouseUp event
                if (iTimeWatcher.ContainsKey(XConstants.ButtonType.LeftButton))
                {
                    iTimeWatcher.Remove(XConstants.ButtonType.LeftButton);
                    (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseUpEventArgs
                    {
                        Button = XConstants.ButtonType.LeftButton,
                        Location = oLocation
                    });
                }
            }

            #endregion

            #region Middle

            // Now, check the left, middle, and right buttons to see if their state is pressed
            if (oCurrentState.MiddleButton == ButtonState.Pressed)
            {

                // Let's see if it's in the dictionary
                if (!iTimeWatcher.ContainsKey(XConstants.ButtonType.MiddleButton))
                {
                    iTimeWatcher.Add(XConstants.ButtonType.MiddleButton, aTime.TotalGameTime);
                    (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseDownEventArgs
                    {
                        Button = XConstants.ButtonType.MiddleButton,
                        Location = oLocation
                    });
                }
                else
                {

                    // It does contain the key; let's check to see if we need to raise an event
                    if (aTime.TotalGameTime - iTimeWatcher[XConstants.ButtonType.MiddleButton] > Duration)
                    {
                        // Raise the event!
                        (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseHeldEventArgs
                        {
                            Button = XConstants.ButtonType.MiddleButton,
                            Location = oLocation,
                            Duration = aTime.TotalGameTime - iTimeWatcher[XConstants.ButtonType.MiddleButton]
                        });
                    }

                }

            }
            else
            {

                // It is not pressed. See if we exist in the collection.
                // If so, remove it and raise the OnMouseUp event
                if (iTimeWatcher.ContainsKey(XConstants.ButtonType.MiddleButton))
                {
                    iTimeWatcher.Remove(XConstants.ButtonType.MiddleButton);
                    (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseUpEventArgs
                    {
                        Button = XConstants.ButtonType.MiddleButton,
                        Location = oLocation
                    });
                }
            }

            #endregion

            #region Right

            // Now, check the left, middle, and right buttons to see if their state is pressed
            if (oCurrentState.RightButton == ButtonState.Pressed)
            {

                // Let's see if it's in the dictionary
                if (!iTimeWatcher.ContainsKey(XConstants.ButtonType.RightButton))
                {
                    iTimeWatcher.Add(XConstants.ButtonType.RightButton, aTime.TotalGameTime);
                    (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseDownEventArgs
                    {
                        Button = XConstants.ButtonType.RightButton,
                        Location = oLocation
                    });
                }
                else
                {

                    // It does contain the key; let's check to see if we need to raise an event
                    if (aTime.TotalGameTime - iTimeWatcher[XConstants.ButtonType.RightButton] > Duration)
                    {
                        // Raise the event!
                        (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseHeldEventArgs
                        {
                            Button = XConstants.ButtonType.RightButton,
                            Location = oLocation,
                            Duration = aTime.TotalGameTime - iTimeWatcher[XConstants.ButtonType.RightButton]
                        });
                    }

                }

            }
            else
            {

                // It is not pressed. See if we exist in the collection.
                // If so, remove it and raise the OnMouseUp event
                if (iTimeWatcher.ContainsKey(XConstants.ButtonType.RightButton))
                {
                    iTimeWatcher.Remove(XConstants.ButtonType.RightButton);
                    (Events as List<EventArgs.MouseActionEventArgs>).Add(new EventArgs.MouseUpEventArgs
                    {
                        Button = XConstants.ButtonType.RightButton,
                        Location = oLocation
                    });
                }
            }

            #endregion

        }

        /// <summary>
        /// Initializes the XMouse class
        /// </summary>
        public void Initialize()
        {
            Duration = XConstants.Default;
            iTimeWatcher = new Dictionary<XConstants.ButtonType, TimeSpan>();
            Events = new List<EventArgs.MouseActionEventArgs>().AsEnumerable();
        }

    }

    /// <summary>
    /// The keyboard class is responsible for handling all key input management
    /// </summary>
    public class XKeyboard : XUpdate
    {

        /// <summary>
        /// The ignore list maintains an internal dictionary that is responsible for holding
        /// and updating a list of keys to ignore based on timespans. A key will be ignored for
        /// at LEAST the given TimeSpan, but may end up lasting longer for a variety of reasons.
        /// </summary>
        private Dictionary<Keys, TimeSpan> iIgnoreList { get; set; }

        /// <summary>
        /// The last pressed list is responsible for keeping track of the keys that were pressed
        /// on the last frame
        /// </summary>
        private Dictionary<Keys, TimeSpan> iLastPressed { get; set; }


        /// <summary>
        /// A collection of mouse events that have occurred since the previous frame
        /// </summary>
        public IEnumerable<EventArgs.KeyActionEventArgs> Events { get; private set; }

        /// <summary>
        /// The duration timespan object is used to delay a keydown event being fired in repetition.
        /// However, if a key is chosen to be ignored, the duration object is NOT taken into account as
        /// ignoring a key is done independently.
        /// </summary>
        public TimeSpan Duration { get; set; }
        
        /// <summary>
        /// Checks to see if a certain key is pressed.
        /// Please note that this method will in fact return true even if the key is being ignored.
        /// </summary>
        /// <param name="aKey">The key to check</param>
        /// <returns></returns>
        public Boolean IsKeyPressed(Keys aKey)
        {
            return Microsoft.Xna.Framework.Input.Keyboard.GetState(PlayerIndex.One).IsKeyDown(aKey);
        }

        /// <summary>
        /// Perform an update to process key changes
        /// </summary>
        /// <param name="aTime">The current GameTime</param>
        public void Update(GameTime aTime)
        {

            // Clear all of the prior event arguments
            (Events as List<EventArgs.KeyActionEventArgs>).Clear();

            // Grab a collection of currently pressed keys and not pressed
            List<Keys> oCurrentlyPressed = Microsoft.Xna.Framework.Input.Keyboard.GetState(PlayerIndex.One).GetPressedKeys().ToList();
            List<Keys> oNotCurrentlyPressed = iLastPressed.Keys.ToList().Except(oCurrentlyPressed).ToList();
            
            // Update the ignore list
            iIgnoreList =
                iIgnoreList.Except
                (
                    iIgnoreList.Where
                    (
                        (k) => aTime.TotalGameTime - k.Value > Duration || !oCurrentlyPressed.Contains(k.Key)
                    )
                ).ToDictionary(k => k.Key, k => k.Value);

            //// Loop through the ignored keys
            //for (int nLoopCount = iIgnoreList.Keys.Count() - 1; nLoopCount >= 0; nLoopCount--)
            //{

            //    // Grab a key
            //    Keys oKey = iIgnoreList.Keys.ToList()[nLoopCount];

            //    // Check the time span+whether or not it's still pressed
            //    if (aTime.TotalGameTime - iIgnoreList[oKey] > Duration || !oCurrentlyPressed.Contains(oKey)) iIgnoreList.Remove(oKey);

            //}

            // Now, remove the ones that need to be ignored still
            oCurrentlyPressed = oCurrentlyPressed.Except(iIgnoreList.Keys).ToList();

            // Now, clear out the previously pressed keys
            iLastPressed.Clear();

            // Update in the last pressed as well as the ignore list
            foreach (Keys oKey in oCurrentlyPressed) { iLastPressed.Add(oKey, aTime.TotalGameTime); }
            foreach (Keys oKey in oCurrentlyPressed) { iIgnoreList.Add(oKey, aTime.TotalGameTime); }

            // Now raise the event with the given collection of keys!
            (Events as List<EventArgs.KeyActionEventArgs>).Add(new EventArgs.KeysPressedEventArgs { Keys = oCurrentlyPressed });
            (Events as List<EventArgs.KeyActionEventArgs>).Add(new EventArgs.KeysReleasedEventArgs { Keys = oNotCurrentlyPressed });

        }

        /// <summary>
        /// Initializes the keyboard class
        /// </summary>
        public void Initialize()
        {
            Duration = XConstants.Default;
            iIgnoreList = new Dictionary<Keys, TimeSpan>();
            iLastPressed = new Dictionary<Keys, TimeSpan>();
            Events = new List<EventArgs.KeyActionEventArgs>().AsEnumerable();
        }

    }



}